package edu.upenn.seas.peerreview.smui.variants

import edu.upenn.seas.peerreview.Id

import scala.swing._
import scala.swing.event._
import java.awt.Color
import javax.swing.{ UIManager, SwingUtilities }

/**
 * Adds some padding to a Swing Component object.
 */ 
trait Padding extends Component {
  border = Swing.EmptyBorder(10)
}

/** 
 * Displays a gameboard with a label representing a single player.
 * @param width
 * @param height
 * @param name the title of the gameboard
 * @param fetcher a function with which to inquire for this board's state
 * @param broadcast a function with which to send button clicks
 */
class BoardView(width: Int, height: Int, name: String, fetcher: () => Board, 
    broadcast: (Int, Int) => Unit) extends BoxPanel(Orientation.Vertical) {
            
  /**
   * Displays the gameboard for a single player. [Nested Class]
   */
  private class BoardPanel() extends GridPanel(height, width) {
    
    /**
     * Nested class: A button that represents an ocean spot.
     * @param i the column (X) value
     * @param j the row (Y) value
     * @param parent the BoardPanel this spot belongs to
     */
    private case class Spot(val i: Int, val j: Int) extends Button {
      val EMPTY_TEXT = "  ?  "
      text = EMPTY_TEXT
      def miss() { 
        text = "<html><font color=green>MISS</font></html>"; 
        enabled = false; 
        opaque = false
      }
      def hit() { 
        text = "<html><font color=red><b>HIT</b></font></html>"; 
        enabled = false; 
        opaque = false
      }
      def ship(v: Vessel) { 
        text = "<html><font color=blue>S(%d)</font></html>".format(v.size);
        enabled = false; 
        opaque = false
      }
      def outOfGame() { 
        enabled = false; 
      }
      
      def wake() {
        if (text == EMPTY_TEXT) enabled = true
      }
      def sleep() {
        if (text == EMPTY_TEXT) enabled = false
      }
      
      reactions += { 
        case ButtonClicked(b) => broadcast(i, j)
      }
    }
    
    private val buttons = (for (j <- 0 until height; i <- 0 until width) 
        yield Spot(i, j)).toList
    buttons.foreach(contents += _)

    // Is this board disabled?
    var isMute = false 
      
    /**
     * Wakes board to be clickable.
     */
    def wake() : Unit = {
      if (!isMute)
      buttons.foreach(_.wake)
    }
    
    /**
     * Disables board to be non-clickable.
     * @param doOutline whether to outline the move coordinate on this board
     * @param move the move that was made (but may not be on this board)
     */
    def sleep(doOutline: Boolean, move: Point) : Unit = {
      if (!isMute) {
        buttons.foreach(_.sleep)
        if (doOutline) {
          buttons(move.y*width + move.x).border = SELECT_BORDER
        }
      }
    }
      
    val EMPTY_BORDER = Swing.EmptyBorder(1)
    val SELECT_BORDER = Swing.LineBorder(java.awt.Color.RED)
      
    /**
     * Updates the board display.
     */
    def update(): Unit = {
      val board = fetcher()
      def mark(i: Int, j: Int, result: CellStatus) {
        val index = j*width + i
        val button = buttons(index)
        button.border = EMPTY_BORDER
        result match {
          case CellStatus.Hit => button.hit()
          case CellStatus.Miss => button.miss()
          case CellStatus.Ship(v) => button.ship(v)
          case _ => if (isMute) button.outOfGame() else None
        }
      }
      for (i <- 0 until width; j <- 0 until height) {
        mark(i, j, board.examine(i, j))
      }
    }
  }
  
  private val myBoard = new BoardPanel()
  border = Swing.EmptyBorder(5)
  visible = false
  contents += new Label(name) with Padding
  contents += myBoard
    
  /**
   * Mutes this board by disabling any further clicks on it.
   */
  def markOut() = (myBoard.isMute = true)
  
  /**
   * Update the board.
   */
  def updateBoard() = myBoard.update()
  
  /**
   * Is this board mute? If so, player == dead.
   */
  def isMute() = myBoard.isMute
    
  def wake() = myBoard.wake()
  def sleep(doOutline: Boolean, move: Point) = myBoard.sleep(doOutline, move)
}

/**
 * A collection of BoardViews with a selector at the bottom.
 * @param fetcher function to get the board for an opponent
 * @param opponents the list of opponents
 * @param width
 * @param height
 */
class OpponentView(fetcher: Id => Board, opponents: List[Id], width: Int, 
    height: Int) extends BoxPanel(Orientation.Vertical) with IdMapper {
  val myself = this
  makeMapping(opponents)
    
  /**
   * The move the player has selected. Public to allow parent to query it.
   */
  var nextMove: (Id, Point) = null
  var prevMove: (Id, Point) = null
  
  // To pass into the BoardView constructors.
  private def storeMove(opp: Id)(i: Int, j: Int) = {
    if (nextMove == null)
      nextMove = (opp, Point(i, j))      
  }
  
  val repeatButton = new Button("Repeat Last Move") {
    def isDead(opp: Id): Boolean = {
      val getIndex = opponents.zipWithIndex.filter(_._1 == opp)
      if (getIndex.isEmpty) true
      else oppBoards(getIndex.head._2).isMute()
    }
    
    reactions += {
      case ButtonClicked(e) => if (prevMove != null && !isDead(prevMove._1)) {
        storeMove(prevMove._1)(prevMove._2.x, prevMove._2.y)
      } else {
        Dialog.showMessage(myself, "Cannot do: no previous move, or that opponent is defeated!")
      }
   }
  }
  
  // Array of opponent boards
  val dropdown = opponents.map(id => " %s".format(mapId(id)))
  private val oppBoards = (for (opp <- opponents) yield 
      new BoardView(width, height, "<html>Opponent Board: <b>" + mapId(opp) + "</b></html>", 
          () => { fetcher(opp)} , this.storeMove(opp) _)
      ).toList
  
  oppBoards.foreach(contents += _)
  oppBoards.head.visible = true
  
  // Selector
  private val boardSelect = new ComboBox(dropdown) {
    border = Swing.LineBorder(java.awt.Color.black)
    maximumSize = new java.awt.Dimension(200, 35)
    preferredSize = maximumSize
    listenTo(this.selection)
    reactions += {
      case SelectionChanged(b) => {
        val selectedIndex = this.selection.index
        viewOpponent(selectedIndex)
      }
    }
  }
  
  def viewOpponent(ind: Int) : Unit = {
    oppBoards.foreach(_.visible = false)
    oppBoards(ind).visible = true
    boardSelect.selection.index = ind
  }
  
  contents += new FlowPanel() {
    border = Swing.EmptyBorder(10)
    contents += new FlowPanel() {
      contents += boardSelect
      contents += repeatButton
    }
  }
  
  // Invalidate an opponent because they are out of the game.
  def invalidate(opp: Id) {
    val getIndex = opponents.zipWithIndex.filter(_._1 == opp)
    if (getIndex.isEmpty) throw new RuntimeException("No such opponent!")
    oppBoards(getIndex.head._2).markOut()
  }
  
  // Move was made, disable opponent boards so you don't double click.
  def sleep(opp: Id, move: Point) {
    oppBoards.zipWithIndex.foreach( tup => {
      tup._1.sleep(opponents(tup._2) == opp, move)
    })
  }
  
  // Update all opponent boards.
  def updateBoards() {
    oppBoards.foreach(b => { b.wake(); b.updateBoard() })
  }
}
    
    
/**
 * Panel that pulls together your board and the opponent boards to present
 * a display for the firing stage of the game.
 * 
 * @param sbar status bar
 * @param logger logger item
 * @param size board size
 * @param fetchBoard
 * @param fetchOppBoards
 * @param parent
 */
class PlayPanel(sbar: StatusBar, 
                logger: Logs,
                opponents: List[Id],
                size: Point,
                shipSizes: List[Int],
                fetchBoard: () => Board, 
                fetchOppBoards: Id => Board,
                parent: SwingDecider) 
    extends BorderPanel() with StatePanel with IdMapper {
      
  val myself: scala.swing.Component = this
  
  makeMapping(opponents)   
  
  // Create your own board.
  private val myBoard = new BoardView(size.x, size.y, "<html><b>Your Board</b></html>", 
      fetchBoard, (i: Int, j:Int) => {})
  myBoard.visible = true
  myBoard.border = Swing.EmptyBorder(15)
  myBoard.markOut()
  
  // Create opponent viewer.
  private val oppViewer = new OpponentView(fetchOppBoards, opponents, size.x, size.y)
  
  // Create report textarea.
  val report = new ScrollPane() {
    border = Swing.TitledBorder(Swing.EtchedBorder, 
       " In the last turn: ")
    var messages: List[String] = List()
    val myText = new TextArea() {
      border = Swing.EmptyBorder(10)
      wordWrap = true
      editable = false
      opaque = false
    }
    contents = myText
    def report(msg: String): Unit = {
      messages ::= msg
      myText.text = messages.reverse mkString "\n"
    }
    def reset(): Unit = {
      messages = List()
    }
  }
  report.report("Fire away!")
  
  // Create the state change button.
  private val continueButton = new Button("View Game Stats >>") {
    enabled = false
    visible = false
    reactions += {
      case ButtonClicked(e) => parent.changeState(4)
    }
  }
  
    
  // Create the ship listings.
  private val shipListing = new Button("See Ship List") {
    reactions += {
      case ButtonClicked(e) => {
        val msg = "Ships in this game:\n" + (shipSizes.map(Vessel(_))
               .map(s => "%s (%d)\n".format(s.name, s.size))
               .mkString(""))
        Dialog.showMessage(myself, msg, "Ship Info", Dialog.Message.Info, null)
      }
    }
  }
  
  // Create opponent buttons.
  private val buttonPanel = new FlowPanel() {
    contents += new Label("<html><b>Current Opponents</b>:</html> ")
    val playerButtons = (for (tup <- opponents.zipWithIndex) yield {
      val (opp, ind) = tup
      new Button(mapId(opp)) {
        reactions += {
          case ButtonClicked(e) => oppViewer.viewOpponent(ind)
        }
        val myId = opp
        var out = false
        def dead() = {
          if (!out) {
            enabled = false
            text = "<html><s>" + this.text + "</s></html>"
          }
          out = true
        }
        def refresh() = if (!out) this.enabled = true 
        def madeMove() = this.enabled = false
      }
    }).toList
    playerButtons.foreach(contents += _)
    contents += continueButton
  }
  
  // Update the boards.
  def update() {
   oppViewer.updateBoards()
   myBoard.updateBoard()
  }

  def enableStateChange() : Unit = {
    opponents.foreach(oppViewer.invalidate(_))    
    update()
    continueButton.visible = true
    continueButton.enabled = true
  }
  
  // Mark a player as out.
  def markPlayerOut(opp: Id) : Unit = {
    oppViewer.invalidate(opp)
    val candidates = buttonPanel.playerButtons.filter(_.myId == opp)
    if (!candidates.isEmpty) candidates.head.dead()                    
  }
  
  // Move made.
  def madeMove(opp: Id) {
    val candidates = buttonPanel.playerButtons.filter(_.myId == opp)
    if (!candidates.isEmpty) candidates.head.madeMove()
  }
  
  // New turn.
  def newTurn() = buttonPanel.playerButtons.foreach(_.refresh())                                                       
  
  // Get the next move.
  def getNextMove(): (Id, Point) = {
    sbar.update("Waiting for your move...")
    oppViewer.nextMove = null
    while (oppViewer.nextMove == null) { 
      Thread.sleep(500)
    }
    val move = oppViewer.nextMove;
    if (move == null) return getNextMove()
    oppViewer.nextMove = null;
    oppViewer.prevMove = move
    oppViewer.sleep(move._1, move._2)
    move
  }
  
  border = Swing.EmptyBorder(5)
  add(new GridPanel(1, 2) {
    contents += new BoxPanel(Orientation.Vertical) {
        border = Swing.EmptyBorder(5)
        contents += myBoard
        contents += report
      }
    contents += new BoxPanel(Orientation.Vertical) {
      contents += shipListing
      contents += oppViewer
    }
  }, BorderPanel.Position.Center)
  add(buttonPanel, BorderPanel.Position.South)
  
  update() // Initial update.
}
    